using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.ConfigurationManagement.ManagementProvider;
using Microsoft.ConfigurationManagement.ManagementProvider.WqlQueryEngine;
using System.Data;
using System.Data.OleDb;
using Log;

namespace SiteBound
{
    class Program
    {
        
        private static Logger log;
        static void Main(string[] args)

        {

            StringDictionary arguments = parseArgs(args);
            if (arguments.Count == 0)
            {
                return; //arguments invalid
            } //end if

            try
            {

                WqlConnectionManager connection;
                OleDbDataAdapter da;
                DataTable dt;
                string[] sitecodes;
                string strsiteserver;
                ArrayList boundaries;
                string strboundarytype;

                strsiteserver = arguments["Server"];

                log = new Logger(arguments["LogFile"]);

                log.addLine("Connecting to Excel Spreadsheet.");
                OleDbConnection con = new OleDbConnection(@"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + arguments["ExcelFile"] + ";Extended Properties=Excel 8.0");

                log.addLine("Querying for Site Codes");
                //get the various site codes from the spreadsheet
                da = new OleDbDataAdapter(@"Select distinct SiteCode from [List$] where SiteCode <> ''", con);
                dt = new DataTable();

                da.Fill(dt);

                sitecodes = new string[dt.Rows.Count];

                log.addLine("Number of Site Codes = " + sitecodes.Length);

                //fill sitecodes array
                for (int i = 0; i < sitecodes.Length; i++)
                {
                    sitecodes[i] = dt.Rows[i][0].ToString();
                    log.addLine("Site Code: " + sitecodes[i]);
                } //end for

                //connect to SMS Provider
                log.addLine("Connecting to SMS Provider at: " + strsiteserver);
                connection = Connect(strsiteserver);

                //add IPRangeBoundaries per site code
                for (int i = 0; i < sitecodes.Length; i++)
                {
                    log.addLine("Querying Excel Spreadsheet for " + sitecodes[i] + " Boundaries.");
                    //da = new OleDbDataAdapter(@"Select Distinct [IP Range Start],[IP Range End],[IP Range Description] from [List$] where SiteCode='" + sitecodes[i] +"'", con);

                    da = new OleDbDataAdapter(@"Select Distinct [Boundary],[Description],[Connection] from [List$] where SiteCode='" + sitecodes[i] + "'", con);

                    dt = new DataTable();
                    da.Fill(dt);

                    boundaries = new ArrayList(dt.Rows.Count*4); // *4 because this ArrayList will contain boundary type and description as well

                    foreach (DataRow row in dt.Rows)
                    {
                        if (!isBoundary(connection, row["Boundary"].ToString(), sitecodes[i]))
                        {
                            strboundarytype = getBoundaryType(row["Boundary"].ToString());
                            if (strboundarytype == "")                            
                            {
                                log.addWarningLine("Skipping boundary " + row["Boundary"].ToString());
                                continue;
                            }//end if
                            boundaries.Add(strboundarytype);
                            boundaries.Add(row["Boundary"].ToString());
                            boundaries.Add(row["Description"].ToString());
                            switch (row["Connection"].ToString().ToUpper())
                            {
                                case "FAST":
                                    boundaries.Add("0");
                                    break;
                                case "SLOW":
                                    boundaries.Add("1");
                                    break;
                                default:
                                    boundaries.Add("0");
                                    log.addWarningLine("Using default value of FAST for connection type. Specified value could not be used.");
                                    break;
                            } //end switch

                        } //end if
                        else
                        {
                            log.addWarningLine("Skipping boundary " + row["Boundary"].ToString());
                        }
                    } //end for each
                    boundaries.TrimToSize();
                    if (boundaries.Count == 0 || boundaries.Count % 4 != 0) //make sure boundaries size is multiple of 4 otherwise somethings wrong
                    {
                        log.addErrorLine("Aborting boundary additions for " + sitecodes[i]);
                        continue;
                    }
                    log.addLine("Beginning to add Boundaries to " + sitecodes[i]);
                    //addIPRangeBoundaries(dt, connection, sitecodes[i]);
                    addIPRangeBoundaries((String[])boundaries.ToArray(typeof(String)), connection, sitecodes[i]);
                } //end for            

                log.addLine("Finished.");

                //OleDbDataAdapter da = new OleDbDataAdapter(@"Select Distinct [IP Range Start],[IP Range End],[IP Range Description],[SiteCode] from [ActiveServerList$]", con);

                // Console.WriteLine();
                //addIPRangeBoundaries(dt);
            }//end try
            catch (Exception e)
            {
                log.addErrorLine(e.Message);
            }
   
        } //end Main

        //private static void addIPRangeBoundaries(DataTable dt, WqlConnectionManager connection, string sitecode) {
        private static void addIPRangeBoundaries(string[] boundaries, WqlConnectionManager connection, string sitecode)
        {
            string[] strarrDetails, strarrDisplayNames, strarrTypes;
            int[] intarrFlags;
            int originalsize;
            int size;

            try
            {

                log.addLine("Accessing SMS_SCI_RoamingBoundary instance.");
                IResultObject siteboundary = connection.GetInstance("SMS_SCI_RoamingBoundary.Filetype=2,Itemtype='Roaming Boundary',Sitecode='" + sitecode + "',ItemName='Roaming Boundary'");

                originalsize = siteboundary["Details"].StringArrayValue.GetUpperBound(0) + 1;
                log.addLine("Current number of boundaries for " + sitecode + " = " + originalsize);
                //log.addLine("Number of boundaries to be added to " + sitecode + " = " + dt.Rows.Count);
                log.addLine("Number of boundaries to be added to " + sitecode + " = " + boundaries.Length/4);
                size = originalsize + boundaries.Length/4;

                strarrDetails = new string[size];
                strarrDisplayNames = new string[size];
                strarrTypes = new string[size];
                intarrFlags = new int[size];

                siteboundary["Details"].StringArrayValue.CopyTo(strarrDetails,0);
                siteboundary["DisplayNames"].StringArrayValue.CopyTo(strarrDisplayNames, 0);
                siteboundary["Types"].StringArrayValue.CopyTo(strarrTypes, 0);
                siteboundary["Flags"].IntegerArrayValue.CopyTo(intarrFlags, 0);

                int i,j;
                for (i = originalsize, j = 0; i < size && j<boundaries.Length; i++,j+=4)
                {
                   //change to boundary field                    

                    strarrTypes[i] = boundaries[j]; //type
                    strarrDetails[i] = boundaries[j+1]; //boundary
                    log.addLine("Adding boundary: " + strarrDetails[i]);
                    strarrDisplayNames[i] = boundaries[j+2]; //description
                    intarrFlags[i] = int.Parse(boundaries[j+3]); //connection 
                    
                }//end for

                siteboundary["Details"].StringArrayValue = strarrDetails;
                siteboundary["DisplayNames"].StringArrayValue = strarrDisplayNames;
                siteboundary["Types"].StringArrayValue = strarrTypes;
                siteboundary["Flags"].IntegerArrayValue = intarrFlags;

                log.addLine("Commiting boundary changes to site control file for " + sitecode);
                siteboundary.Put();
                log.addLine("Boundary changes commmited!");
            }
            catch (SmsException e)
            {
                log.addErrorLine("Failed to add boundaries for : " + sitecode);
                log.addErrorLine(e.Message);
            }
            catch (Exception e)
            {
                log.addErrorLine("Failed: " + e.Message);
            }


        } //end addIPRangeBoundaries

        private static WqlConnectionManager Connect(string serverName)
        {
            try
            {
                SmsNamedValuesDictionary namedValues = new SmsNamedValuesDictionary();
                WqlConnectionManager connection = new WqlConnectionManager(namedValues);

                if (System.Net.Dns.GetHostName().ToUpper() == serverName.ToUpper())
                {
                    // Connect to local computer.
                    connection.Connect(serverName);
                }//end if
                else
                {
                    // Connect to remote computer.
                    connection.Connect(serverName);
                }//end else

                return connection;
            }//end try
            catch (SmsException e)
            {
                log.addErrorLine("Failed to Connect. Error: " + e.Message);
                return null;
            }//end catch
            catch (UnauthorizedAccessException e)
            {
                log.addErrorLine("Failed to authenticate. Error:" + e.Message);
                throw;
            }//end catch
        }//end Connect()

        private static bool isBoundary(WqlConnectionManager connection, string strboundary, string strsitecode)
        {
            try
            {
                IResultObject results = connection.QueryProcessor.ExecuteQuery("select Value from SMS_Boundary where Value='" + strboundary + "' AND SiteCode='" + strsitecode + "'");

                foreach (IResultObject result in results)
                {
                    log.addLine(strboundary + " is a boundary in Site: " + strsitecode);
                    return true;

                } //end foreach
                //no results if execution reaches here
                log.addLine(strboundary + " is not a boundary in Site: " + strsitecode);
                return false;
            }//end try
            catch (SmsException e)
            {
                log.addErrorLine(e.Message);
                log.addWarningLine("Could not determine if " + strboundary + " is a boundary in Site: " + strsitecode);
                return true;
            }//end catch
            catch (Exception e)
            {
                log.addErrorLine(e.Message);
                log.addWarningLine("Could not determine if " + strboundary + " is a boundary in Site: " + strsitecode);
                return true;
            }//end catch
        } //end isBoundary()

        private static string getBoundaryType(string strboundary)
        {
            string strboundarytype="";

            //ip regular expressions were obtained from http://blogs.msdn.com/mpoulson/archive/2005/01/10/350037.aspx

            Regex iprangepattern = new Regex(@"\A(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}(\-)(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\z", RegexOptions.IgnoreCase);
            Regex ipsubnetpattern = new Regex(@"\A(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\z", RegexOptions.IgnoreCase);
            Regex adsitepattern = new Regex(@"^[a-z0-9]+$", RegexOptions.IgnoreCase);

            //HEX Compressed
            Regex ipv6pattern1 = new Regex(@"\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)\z", RegexOptions.IgnoreCase);
            //6 HEX 4 Dec
            Regex ipv6pattern2 = new Regex(@"\A((?:[0-9A-Fa-f]{1,4}:){6,6})(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\z", RegexOptions.IgnoreCase);
            //HEX 4 Dec Compressed
            Regex ipv6pattern3 = new Regex(@"\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::((?:[0-9A-Fa-f]{1,4}:)*)(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\z", RegexOptions.IgnoreCase);

            Regex ipv6pattern4 = new Regex(@"\A(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\z", RegexOptions.IgnoreCase);

            if (iprangepattern.Match(strboundary).Success)
            {
                strboundarytype = "IP Ranges";
            }
            else if (ipsubnetpattern.Match(strboundary).Success)
            {
                strboundarytype = "IP Subnets";
            }
            else if (adsitepattern.Match(strboundary).Success)
            {
                strboundarytype = "AD Site Name";            
            }
            else if (ipv6pattern1.Match(strboundary).Success || ipv6pattern2.Match(strboundary).Success || ipv6pattern3.Match(strboundary).Success || ipv6pattern4.Match(strboundary).Success)
            {
                strboundarytype = "IPv6 Prefixes";
            }
            else
            {
                log.addErrorLine("Could not determine boundary type for " + strboundary);
                return strboundarytype;
            }

            log.addLine("Boundary type for " + strboundary + " is " + strboundarytype);
            return strboundarytype;

        } //end getBoundaryType


        //just messing around here
        private static StringDictionary parseArgs(string[] args)
        {
            
            Regex parameters = new Regex(@"(?<paramname>(/s|/file|/log))[=:\s](?<value>[:\\\.a-z0-9]*)", RegexOptions.IgnoreCase);
            string all = "";
            StringDictionary arguments = new StringDictionary();

            foreach (string str in args)
            {
                all = all + str + " ";
            }//end foreach

            MatchCollection matches = parameters.Matches(all.Trim());
            if (matches.Count == 3)
            {

                foreach (Match match in matches)
                {

                    if (match.Success)
                    {
                        switch (match.Groups["paramname"].Value.ToUpper())
                        {
                            case "/S":
                                arguments.Add("Server", match.Groups["value"].Value);
                                break;
                            case "/FILE":
                                arguments.Add("ExcelFile", match.Groups["value"].Value);
                                break;
                            case "/LOG":
                                arguments.Add("LogFile", match.Groups["value"].Value);
                                break;
                        }//end switch
                    } //end if

                }//end for each
            } //end if
            else
            {
                Console.WriteLine("Usage: ");
                Console.WriteLine("");
                Console.WriteLine(System.Reflection.Assembly.GetExecutingAssembly().GetName().Name + ".exe /s [central site server] /file [excel file] /log [path]");
                Console.WriteLine("");
                Console.WriteLine("/s \t Central Site Server name.");
                Console.WriteLine("/file \t Path to Excel files.");
                Console.WriteLine("/log \t Log file path.");

            } //end else           

            return arguments;

        }//end args Parse

    } //end class
} //end namespace
